﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Corecalc;
using System.Diagnostics;

namespace Sisal
{
    public enum SisalState { NotReferenced, Referenced }

    public abstract class SisalNode 
    {
        public static Dictionary<FullCellAddr, SisalNode> nodeEnvironment = new Dictionary<FullCellAddr, SisalNode>();

        public Task Task { get; set; }
        public Task TempTask { get; set; }
        public Func<SisalNode> Parent { get; set; }

        public List<SisalNode> InputEdges { get; set; }   
        public List<SisalNode> OutputEdges { get; set; }

        public double Overhead { get; set; }
        public double CP { get; set; }

        public virtual double ExecutionTimeCost { get; set; }

        public int T_in { get; set; }
        public virtual int T_out { get; set; }

        public int F { get { return 1; } }      //Only set if IsFormulaNode is true
        public FullCellAddr Fca { get; set; }
        public bool IsFormulaNode { get; set; }
        public virtual SisalState State { get; set; }

        public bool Marked { get; set; }        //For topological sorting
        public bool TempMarked { get; set; }    //For topological sorting

        public int InpComCost {
            get { return InputEdges.Sum(e => e.OutComCost); }
        }

        public int OutComCost {
            get { return 0; }
        }

        public SisalNode() {
            InputEdges = new List<SisalNode>();
            OutputEdges = new List<SisalNode>();

            T_in = InputEdges.Sum(x => x.T_out);
        }

        //Returns all nodes in the graph with this node as its root
        //Does NOT include subgraphs of compound and parallel nodes
        public virtual List<SisalNode> GetGraphNodes() {
            List<SisalNode> ns = new List<SisalNode>();
            ns.Add(this);

            foreach (SisalNode inp in InputEdges) {
                if (!inp.IsFormulaNode) {
                    ns.AddRange(inp.GetGraphNodes());
                }
            }

            return ns;
        }

        public bool DFS(Action<SisalNode> f) {
            f(this);
            foreach (SisalNode node in OutputEdges) {
                f(node);
            }
            foreach (SisalNode node in OutputEdges) {
                node.DFS(f);
            }
            return false;
        }

        //Returns all nodes properly contained in this node (only relevant for compound and parallel nodes)
        //All other nodes return the entire graph rooted in this node.
        public virtual List<SisalNode> GetNodeNodes() {
            return GetGraphNodes();
        }

        public virtual bool IsValueNode { get { return false; } }

        public override string ToString() {
            return string.Format("[{0}, {1}, {2}, {3}, {4}, {5}]:\t", Fca.ca, IsFormulaNode, ExecutionTimeCost.ToString().Replace(",", "."), T_in, T_out, Parent() == this ? Fca.ToString() : Parent().Fca.ToString() + " " + Parent().GetType().ToString());
        }
    }

    class SimpleNode : SisalNode
    {

    }

    class CompoundNode : SisalNode
    {
        public List<SisalNode> Graphs { get; set; }

        public CompoundNode() {
            Graphs = new List<SisalNode>();
        }

        //NB: Overridden in class IfNode (takes MAX of true / false)
        public virtual double ComputeExecutionTimeCost() {
            return Graphs.Sum(x => x.ExecutionTimeCost);
        }

        public override List<SisalNode> GetNodeNodes() {
            CompoundNode node = this as CompoundNode;
            List<SisalNode> nodes = new List<SisalNode>();
            node.Graphs.ForEach(g => nodes.AddRange(g.GetNodeNodes()));
            return nodes;
        }
    }

    // Represents genuine calls to built-in functions. 
    // Calls like "SUM(A1:B2)", "AVG(A1:B)" etc.
    class BuiltInFunctionCallNode : SisalNode
    {
        public string Name { get; set; }
        public SisalNode[] Args { get; set; }
        public int LastInputSize { get; set; }
        public bool Par { get; set; }

        public BuiltInFunctionCallNode(string name, SisalNode[] args) {
            Name = name;
            Args = args;
            LastInputSize = 0;

            foreach (SisalNode arg in args) {
                InputEdges.Add(arg);
                arg.OutputEdges.Add(this);
            }
        }

        public override string ToString() {
            return base.ToString() + string.Format("BuiltInFunctionCallNode [name: {0}]", Name);
        }
    }

    //public class FormulaNode : SisalNode
    //{
    //    public FullCellAddr Fca { get; set; }
    //    public SisalState state;
    //    public SisalNode Root { get; set; }           // Expr -> Graph
    //    public Func<int, int, int, Formats, string> Show { get; set; }

    //    public FormulaNode(FullCellAddr fca, SisalNode root, Func<int, int, int, Formats, string> toString) {
    //        Fca = fca;
    //        Root = root;
    //        Show = toString;
    //        state = SisalState.NotReferenced;
    //        CommunicationCost = root.CommunicationCost;
    //    }

    //    public Cell Cell { get { return Fca.sheet[Fca.ca]; } }

    //    public override double ExecutionTime {
    //        get { return Root.ExecutionTime; }
    //    }

    //    public override string ToString() {
    //        return string.Format("FormulaNode [{0},{1}] (Ft: {2}) ={3}", Fca.ca.col, Fca.ca.row, ExecutionTime, Show(Fca.ca.col, Fca.ca.row, 0, new Formats { ArgDelim = ',' }));
    //    }
    //}

    class ParallelNode : SisalNode
    {
        public double F { get; set; }
        public int Size { get; set; }
        public SisalNode Graph { get; set; }

        public ParallelNode(SisalNode g) {
            Graph = g;
            F = 1.0;
        }

        public override List<SisalNode> GetNodeNodes() {
            throw new System.NotImplementedException();
        }
    }


    //Represents "calls" to an area of cells, i.e. gets the values of many cells
    class MultipleFunctionCallNode : SisalNode
    {
        public FullCellAddr UlFca { get; set; }
        public FullCellAddr LrFca { get; set; }
        public bool Par { get; set; }

        public MultipleFunctionCallNode(FullCellAddr ulFca, FullCellAddr lrFca, FullCellAddr hostFca) {
            UlFca = ulFca;
            LrFca = lrFca;
            Fca = hostFca;

            T_out = FullCellAddr.AreaSize(ulFca, lrFca) * CommunicationCosts.Get("double");
        }

        public void ForEachCellInArea(Action<FullCellAddr> f) {
            for (int c = UlFca.ca.col; c <= LrFca.ca.col; c++) {
                for (int r = UlFca.ca.row; r <= LrFca.ca.row; r++) {
                    f(new FullCellAddr(UlFca.sheet, c, r));
                }
            }
        }

        public double ComputeExecutionTimeCost() {
            double executionTimeCost = 0.0;
            ForEachCellInArea(fca => {
                if (Nodes.ContainsKey(fca)) {
                    SisalNode referredNode = Nodes.Get(fca);
                    if (referredNode.State == SisalState.NotReferenced) {
                        executionTimeCost += referredNode.ExecutionTimeCost;
                        referredNode.State = SisalState.Referenced;
                    }
                }
            });
            return executionTimeCost;
        }

        public override string ToString() {
            return base.ToString() + string.Format("MultipleFunctionCallNode [OutputEdges: {0}]", OutputEdges.Count);
        }
    }

    // Represents "calls" to other cells, i.e. cell value getters
    class FunctionCallNode : SisalNode
    {
        public FullCellAddr ReferredCellAdr { get; set; }
        public bool Par { get; set; }

        public FunctionCallNode(FullCellAddr fca) {
            ReferredCellAdr = fca;

            T_out = CommunicationCosts.Get("double");
        }

        public bool RefersToFormulaNode {
            get { return Nodes.ContainsKey(ReferredCellAdr); }
        }

        public SisalNode Callee {
            get { return Nodes.Get(ReferredCellAdr); }
        }

        public override string ToString() {
            return base.ToString() + string.Format("FunctionCallNode [cell: {0}, par: {1}]", ReferredCellAdr, Par);
        }
    }

    class BinOperator : SimpleNode
    {
        public string Operator  { get; set; }
        public SisalNode Left { get; set; }
        public SisalNode Right { get; set; }

        public BinOperator(string binOp, SisalNode left, SisalNode right) {
            Operator = binOp;
            Left = left;
            Right = right;

            this.InputEdges.Add(left);
            this.InputEdges.Add(right);

            left.OutputEdges.Add(this);
            right.OutputEdges.Add(this);

            T_out = CommunicationCosts.Get("double");
            ExecutionTimeCost = ExecutionTimes.Get(binOp);
        }

        public override string ToString() {
            return base.ToString() + string.Format("BinOperator [{0}]", Operator);
        }
    }

    class IfNode : CompoundNode
    {
        public IfNode(SisalNode cond, SisalNode trueBranch, SisalNode falseBranch) {
            InputEdges.Add(cond);
            cond.OutputEdges.Add(this);

            trueBranch.OutputEdges.Add(this);
            falseBranch.OutputEdges.Add(this);

            InputEdges.Add(trueBranch);
            InputEdges.Add(falseBranch);

            Graphs.Add(trueBranch);
            Graphs.Add(falseBranch);

            T_out = Math.Max(trueBranch.T_out, falseBranch.T_out);
        }

        public override double ComputeExecutionTimeCost() {
            double @true = Graphs[0].ExecutionTimeCost;
            double @false = Graphs[1].ExecutionTimeCost;

            return Math.Max(@true, @false);
        }

        public override string ToString() {
            return base.ToString() + string.Format("IfNode: [InputEdges: {0}, OutputEdges: {1}]", InputEdges.Count, OutputEdges.Count);
        }
    }

    class ValueNode<T> : SimpleNode 
    {
        public T Value { get; set; }

        public ValueNode(T value, int comCost) {
            Value = value;

            T_out = comCost;
            ExecutionTimeCost = ExecutionTimes.Get("Const");
        }

        public override string ToString() {
            return base.ToString() + string.Format("ValueNode [value: {0}]", Value.ToString());
        }

        public override bool IsValueNode {
            get { return true; }
        }
    }
}
